const { SyncHook, SyncBailHook, AsyncParallelHook, AsyncSeriesHook } = require('tapable');
const Compilation = require('./Compilation');
// const ContextModuleFactory = require('./ContextModuleFactory');
// const NormalModuleFactory = require('./NormalModuleFactory');

class PackEngine {
    constructor(context) {
        this.hooks = Object.freeze({
            /** @type {SyncHook<[]>} */
            initialize: new SyncHook([]),

            /** @type {AsyncSeriesHook<[Stats]>} */
            done: new AsyncSeriesHook(['stats']),
            /** @type {AsyncSeriesHook<[Compiler]>} */
            run: new AsyncSeriesHook(['compiler']),
            /** @type {AsyncSeriesHook<[Compilation]>} */
            emit: new AsyncSeriesHook(['compilation']),
            /** @type {AsyncSeriesHook<[string, AssetEmittedInfo]>} */
            assetEmitted: new AsyncSeriesHook(['file', 'info']),
            /** @type {AsyncSeriesHook<[Compilation]>} */
            afterEmit: new AsyncSeriesHook(['compilation']),

            /** @type {SyncHook<[Compilation, CompilationParams]>} */
            thisCompilation: new SyncHook(['compilation', 'params']),
            /** @type {SyncHook<[Compilation, CompilationParams]>} */
            compilation: new SyncHook(['compilation', 'params']),

            // /** @type {SyncHook<[NormalModuleFactory]>} */
            // normalModuleFactory: new SyncHook(["normalModuleFactory"]),
            // /** @type {SyncHook<[ContextModuleFactory]>}  */
            // contextModuleFactory: new SyncHook(["contextModuleFactory"]),

            /** @type {SyncHook<[CompilationParams]>} */
            compile: new SyncHook(['params']),
            /** @type {AsyncParallelHook<[Compilation], Module>} */
            make: new AsyncParallelHook(['compilation']),

            // TODO the following hooks are weirdly located here
            // TODO move them for webpack 5
            // /** @type {SyncHook<[Compiler]>} */
            // afterPlugins: new SyncHook(["compiler"]),
            // /** @type {SyncBailHook<[string, Entry], boolean>} */
            // entryOption: new SyncBailHook(["context", "entry"])
        });

        // this.webpack = webpack;

        /** @type {string=} */
        this.name = undefined;
        /** @type {Compilation=} */
        this.parentCompilation = undefined;
        /** @type {Compiler} */
        this.root = this;
        /** @type {string} */
        this.outputPath = '';
        /** @type {Watching} */
        this.watching = undefined;

        /** @type {OutputFileSystem} */
        this.outputFileSystem = null;
        /** @type {IntermediateFileSystem} */
        this.intermediateFileSystem = null;
        /** @type {InputFileSystem} */
        this.inputFileSystem = null;
        /** @type {WatchFileSystem} */
        this.watchFileSystem = null;

        /** @type {string|null} */
        this.recordsInputPath = null;
        /** @type {string|null} */
        this.recordsOutputPath = null;
        this.records = {};
        /** @type {Set<string>} */
        this.managedPaths = new Set();
        /** @type {Set<string>} */
        this.immutablePaths = new Set();

        /** @type {Set<string>} */
        this.modifiedFiles = undefined;
        /** @type {Set<string>} */
        this.removedFiles = undefined;
        /** @type {Map<string, FileSystemInfoEntry | null>} */
        this.fileTimestamps = undefined;
        /** @type {Map<string, FileSystemInfoEntry | null>} */
        this.contextTimestamps = undefined;

        /** @type {ResolverFactory} */
        // this.resolverFactory = new ResolverFactory();

        this.infrastructureLogger = undefined;

        /** @type {WebpackOptions} */
        this.options = /** @type {WebpackOptions} */ ({});

        this.context = context;

        // this.requestShortener = new RequestShortener(context, this.root);

        // this.cache = new Cache();

        this.compilerPath = '';

        /** @type {boolean} */
        this.running = false;

        /** @type {boolean} */
        this.idle = false;

        /** @type {boolean} */
        this.watchMode = false;

        /** @private @type {WeakMap<Source, { sizeOnlySource: SizeOnlySource, writtenTo: Map<string, number> }>} */
        this._assetEmittingSourceCache = new WeakMap();
        /** @private @type {Map<string, number>} */
        this._assetEmittingWrittenFiles = new Map();
    }

    createNormalModuleFactory() {
        const normalModuleFactory = new NormalModuleFactory({
            context: this.options.context,
            fs: this.inputFileSystem,
            // resolverFactory: this.resolverFactory,
            options: this.options.module || {},
            associatedObjectForCache: this.root,
        });
        // this.hooks.normalModuleFactory.call(normalModuleFactory);
        return normalModuleFactory;
    }

    createContextModuleFactory() {
        const contextModuleFactory = new ContextModuleFactory(this.resolverFactory);
        // this.hooks.contextModuleFactory.call(contextModuleFactory);
        return contextModuleFactory;
    }

    newCompilationParams() {
        const params = {
            normalModuleFactory: this.createNormalModuleFactory(),
            contextModuleFactory: this.createContextModuleFactory(),
        };
        return params;
    }

    createCompilation() {
        return new Compilation(this);
    }

    newCompilationEngine = () => {
        const compilation = this.createCompilation();
        compilation.name = this.name;
        compilation.records = this.records;
        this.hooks.thisCompilation.call(compilation, params);
        this.hooks.compilation.call(compilation, params);
        return compilation;
    };

    run() {
        console.log('----------- new compilation ------------');
        const compilation = this.newCompilationEngine();

        console.log('---------- make hook ------------');
    }
}

module.exports = PackEngine;
